In Part 1 we defined a set of operating system classes we have as general choices when faced with deciding on what operating system to use in new project. The choices were
- No Operating System
- Pre-emptive Kernel (embedded)
- Real time operating system (RTOS)
- General purpose operating system
And in Part 1 we discussed choice one “No Operating System” (NOS). In reality a NOS solution can be considered an operating system as we discussed you can still have tasks, semaphores and all our favorite system services. In this part we’ll look at the next level up the “Pre-emptive Kernel” and discuss when you should consider this choice in your project.
As review, what does pre-emptive mean? Pre-emptive is a common method of providing multitasking capabilities via a time slice. The system has a kernel that has a system timer dedicated to slicing up execution into small chunks running a scheduler that selects the next task or process to run and switching the running context to that task or process. Sidebar: threads, tasks, processes are all similar operating system concepts whereby multiple “threads” of execution perform “tasks” such that all the “tasks” execute simultaneously. In reality there is one processor (forgetting about multiprocessor systems for now) that’s quickly switching between contexts for each thread, task or process.
In the embedded world we typically talk about threads or tasks while in the general computing world its more common to talk about threads and processes, but the exact nomenclature is typically defined by the operating system architect and it may vary greatly. In general, threads refer to switching with a minimal context that may just be registers and a stack saved for each thread. Processes are typically “heavy” multitasking and usually include a separate memory space and protected resources, so the context switch between processes is usually much more CPU expensive then a thread. For example, UNIX kernel knows about processes but threads are separate multitasking facility provided in a user space process by a library outside the kernel. In larger embedded systems running Linux or VxWorks or a POSIX system, which will typically have tasks that are the same concept as a process. This is illustrated in Figure 4 below.
For small embedded systems there’s typically be only one level of multitasking which will be light weight threads, simply due to the lack of resources to implement more. So now that I have really confused you let’s answer what pre-emptive means to our decision process.
Pre-emptive tasks refer to a kernel that accepts event signals, typically originating from interrupt routines, saves the current context and runs the scheduler to find which thread or process should run next. Pre-emptive events might be an interrupt routine changing a resource semaphore or sending a message to a worker thread that would then be given higher priority by the scheduler. We won’t delve into scheduling in much detail now but just know it’s a routine that looks at all the threads or processes and decides which one should run next based on the priorities of each thread or process.
Note that pre-emptive events in most larger systems occur based on the system timer (typically once every millisecond) which is called time slicing. However, you can use a pre-emptive system without a time-slice and that’s referred to as run-to-completion because every task or thread call runs until its done and the scheduler is only called on the return. Time slicing makes the system better suited for processes that run in parallel where some of the code is from a 3rd party. In a run-to-completion system you need some level of knowledge about the all the other tasks otherwise a rogue task might starve the others for CPU. In a run-to-completion system only interrupts can cause a task to stop running and execution to switch to another task.
So, we can final answer under what conditions to use pre-emptive multitasking:
- If you have third party code that is hard to call without impacting other processes or threads you should use a pre-emptive system so that third party code can’t impact performance of the other tasks. In this case you probably will be better off with a time-slice system link Linux.
- If your system loads and runs modules at run time dynamically based on a script or configuration it may not be possible to test all the possible cases and using pre-emptive tasking should make the system performance more predictable.
- If you have some real time requirements at the task level, then using a pre-emptive system will make it possible to guarantee meeting timing restrictions. If you only have real time issues that can be handled by the interrupt routines then pre-emptive multi-tasking is not required.
So, what are some reasons why you do NOT want pre-emptive multitasking? Pre-emptive task switching and in particular time slicing comes with considerable overhead. Each extra time slice costs you CPU time and the scheduler may introduce delays running tasks that could have waited. A purely event driven system where you can carefully restrict the task call time will have the most efficient CPU usage, but you need to know what each task is doing to ensure all the tasks get enough CPU.
Lastly pre-emptive systems are harder to debug and there are more cases where tasks can be in deadlock situations that are hard to figure out. And usually those problems are corner cases that may take long periods of time to surface.
So, in summary, most medium to large systems will benefit from having pre-emptive multitasking but where you don’t need it save yourself CPU, memory and debugging hassles. A good option for most smaller embedded systems is a pre-emptive run to completion operating system.
Next we’ll look at Real Time Operating Systems (RTOS).
Cheers!